package indi.lddsdu.servlet;

import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.RequestDispatcher;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.RAMDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wltea.analyzer.lucene.IKAnalyzer;
import indi.lddsdu.bean.Item;
import indi.lddsdu.bean.Page;

/**
 * Created by jack on 18/6/10.
 */
public class QueryServlet extends javax.servlet.http.HttpServlet {

    private static final String ERROR_JSP = "/WEB-INF/error.jsp";           //这里千万记得改为常量表示字符串，魔法值！！！
    private static final String TEMPLATE_JSP = "/WEB-INF/template.jsp";
    private static final String INDEX_JSP = "index.jsp";
    private static final String INDEX_DIR = "/Users/jack/Desktop/index_htmlView";
    private Logger logger = LoggerFactory.getLogger(QueryServlet.class);
    private Directory directory = null;
    private boolean errorhappend = false;
    private Exception exception = null;
    private Analyzer analyzer;
    private IndexSearcher indexSearcher = null;
    private QueryParser parser = null;
    private final int PAGE_SIZE = 10;
    //　设定了document中包含的属性有 title , content
    private final String[] FIELDS = {"title","content"};
    private final BooleanClause.Occur[] CLAUSES = {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};

    public QueryServlet() {
        System.out.println("init");
        try {
            //将fs改为ram，降低io代价. 这里是对于已经构建了索引的情况
            directory = new RAMDirectory(FSDirectory.open(Paths.get(INDEX_DIR)), new IOContext());
        }catch (IOException e){
            logger.error("error ", e);
            errorhappend = true;
            this.exception = e;
            return;
        }
        analyzer = new IKAnalyzer();
        DirectoryReader ireader = null;

        try {
            ireader = DirectoryReader.open(directory);
        } catch (IOException e) {
            logger.error("error", e);
            errorhappend = true;
            this.exception = e;
            return;
        }

        this.indexSearcher = new IndexSearcher(ireader);
        //从content field中开始检索
//        不使用MultiFieldParser
//        this.parser = new QueryParser("content", analyzer);

        String[] fields = {"title", "content"};
        BooleanClause.Occur[] clauses = {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};
        this.parser = new MultiFieldQueryParser(fields, analyzer);

        //添加关机处理程序
        Runtime.getRuntime().addShutdownHook(new CtrlCHandler(ireader, directory, analyzer));
    }

    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
            throws javax.servlet.ServletException, IOException {

        String keyWord = request.getParameter("kw");

        //通过keyWord == null 表示用户目前还没有检索信息，转向首页 index.jsp
        if(keyWord == null || keyWord.trim().equals("")){
            request.getRequestDispatcher(INDEX_JSP).forward(request, response);
            return;
        }

        String currenPaggeStr = request.getParameter("p");
        String queryTest = keyWord;
        int currentPage = 1;

        if(currenPaggeStr == null || currenPaggeStr.trim() == "") {

        }else{
            currentPage = Integer.valueOf(currenPaggeStr);
        }

        boolean hasExecError = false;
        Page page = null;
        try {
            page = query(queryTest, currentPage);
        } catch (ParseException e) {
            logger.error("error ", e);
            hasExecError = true;
        }catch (IOException e){
            logger.error("error", e);
            hasExecError = true;
        }

        logger.info("page {}", page.toString());

        //在执行时出错,导向错误的页面, error.jsp
        if (hasExecError){
            RequestDispatcher requestDispatcher = request.getRequestDispatcher(ERROR_JSP);
            requestDispatcher.forward(request, response);
        }else {
            //执行结果正确，将Page结果导向template.jsp
            request.setAttribute("page", page);
            request.setAttribute("keyWord", keyWord);
            RequestDispatcher requestDispatcher = request.getRequestDispatcher(TEMPLATE_JSP);
            requestDispatcher.forward(request, response);
        }
    }

    /**
     * 查询获得所需要显示的页面数据
     * @param queryText     需要检索的参数
     * @param currentPage   想要从第几页开始显示
     * @return {Code Page}  回查询结果page
     * @throws ParseException
     * @throws IOException
     */
    private Page query(String queryText, int currentPage) throws ParseException, IOException {
        List<Item> allItems = null;
        long start;
        long end;
        long timeUsed;
        int hitsLength;

        Query query = null;
        try {
            //query = parser.parse(queryText);
            query = MultiFieldQueryParser.parse(queryText, FIELDS, CLAUSES, analyzer);
        } catch (ParseException e) {
            logger.error("error ", e);
            throw e;
        }

        start = System.currentTimeMillis();

        ScoreDoc[] hits = null;
        try {
            //ScoreDocs[]   返回了得分最高的1000个单项
             hits = indexSearcher.search(query, null, 1000).scoreDocs;
        } catch (IOException e) {
            logger.error("error ", e);
            throw e;
        }

        end = System.currentTimeMillis();
        timeUsed = end - start;
        hitsLength = hits.length;
        allItems = new ArrayList<Item>(hitsLength);
        QueryScorer queryScorer = new QueryScorer(query, "content");       //计分员
        SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<em>", "</em>");
        //策略模式： 要怎么排序，按照什么规则，怎样标识出关键字
        Highlighter highlighter = new Highlighter(htmlFormatter, queryScorer);  //对于构造Highlighter，需要传入htmlFormatter, queryScorer两个对象
        highlighter.setTextFragmenter(new SimpleSpanFragmenter(queryScorer));
        Fragmenter fragmenter = new SimpleFragmenter(250);          //用来设置highlighter的规模
        highlighter.setTextFragmenter(fragmenter);

        //通过直接跳过不用展示的部分来加快速度
        for (int i = 0 ; i < hitsLength; i ++){
            if (i >= (currentPage - 1) * PAGE_SIZE & i < Math.min(hits.length, currentPage * PAGE_SIZE)) {
                Document document = indexSearcher.doc(hits[i].doc);     //这里的hits[i].doc指的是什么？？？    为int类型
                String title = document.get("title");
                String content = document.get("content");
                String link = document.get("link");

                try {
                    content = highlighter.getBestFragment(analyzer, "content", content);
                    title = highlighter.getBestFragment(analyzer, "title", title);
                    //可能存在没有在title中找到关键字的情况，这里会将title置为null
                    if (title == null){
                        title = document.get("title");
                    }
                } catch (InvalidTokenOffsetsException e) {
                    //这里为什么要报错呢???直接忽略
                    logger.error("error", e);
                }

                if (title == null){
                    title = "title (自动填充)";
                }

                Item item = new Item(title, content, link);
                allItems.add(item);

            }else{
                allItems.add(null);
            }
        }

        Page page = new Page(PAGE_SIZE, currentPage, hitsLength, timeUsed, allItems);
        return page;
    }

    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
            throws javax.servlet.ServletException, IOException {
        doPost(request, response);
    }

    /**
     * 关机hook，用来在关机的情况下来close一些必须释放的资源
     */
    private class CtrlCHandler extends Thread{
        private Closeable[] closeables;

        public CtrlCHandler(Closeable... closeables) {
            this.closeables = closeables;
        }

        @Override
        public void run() {
            for (Closeable closeable : closeables) {
                try {
                    if (closeable == null){
                        continue;
                    }
                    System.out.println(closeable + " going to close ");
                    closeable.close();
                } catch (IOException e) {
                    System.out.println("error : " + e);
                }
            }
            System.out.println("shutdown ...");
        }
    }
}
